/*
   Copyright (C) <2009>

   <Rishi B Agrawal> <Vineet Agarwal> <Rohit Sharma>
   <rishi.b.agrawal@gmail.com> <checkout.vineet@gmail.com>
   <imreckless@gmail.com>

   This program is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation, either version 3 of the License, or (at your
   option) any later version->

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE->  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/kernel.h>
#include <linux/buffer_head.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/ext2_fs.h>
#include <linux/ext2_fs_sb.h>
#include <linux/path.h>
#include <linux/list.h>
#include <linux/dcache.h>
#include <../fs/ext2/ext2.h>
#include <linux/time.h>
#include "../include/ohsm.h"
#include "ohsm_k.h"
#include <linux/quotaops.h>
#include <linux/sched.h>
#include <linux/capability.h>

#define DEVICE_NAME "ohsm"

int ohsm_enable_debug;
struct ohsm_log_table_t ohsm_log_table[OHSM_LOG_MAX] = {
  {OHSM_LOG_RELOCATE, "Relocate"},
  {OHSM_LOG_STATS, "Stats"},
   {OHSM_LOG_GENERIC, "Generic"}
};

module_param(ohsm_enable_debug, int, 0);
MODULE_PARM_DESC(ohsm_enable_debug, "Enable debugging for ohsm module.");

static int device_ioctl(struct inode *, struct file *, unsigned int,
		unsigned long);

/****************************************************************
 *                                                              *
 * This function is used to return the ohsm_home_tid            *
 * for a requested inode. Along with the access and             *
 * modification time.                                           *
 *                                                              *
 ***************************************************************/

int
ohsm_get_inode_info(struct ohsm_inode_info *inode)
{
	struct inode *temp_inode;
	temp_inode = ext2_iget(ohsm_sb, inode->i_ino);
	if (!temp_inode)
		return OHSM_ERR_FS;

	inode->ohsm_home_tid = EXT2_I(temp_inode)->ohsm_home_tid;
	inode->i_atime.tv_sec = temp_inode->i_atime.tv_sec;
	inode->i_mtime.tv_sec = temp_inode->i_mtime.tv_sec;
	return 0;
}

void
reset_sam(void)
{

	int i, j;

	ohsm_debug(OHSM_LOG_GENERIC, "Resetting SAM table.\n");
	for (i = 0; i < OHSM_MAX_TIER_ALLOWED; i++) {
		for (j = 0; j < (OHSM_MAX_PV_PER_LVM * 2 + 1); j++)
			sam[i][j] = 0;
	}
}

/****************************************************************
 *                                                              *
 * ohsm_disable () disables OHSM and free all data              *
 * structures allocated                                         *
 *                                                              *
 ***************************************************************/

int
ohsm_disable(void)
{

	if (!ohsm_enabled)
		return OHSM_ERR_ALREADY_DISABLED;

	ohsm_enabled = 0;
	ohsm_sb = NULL;
	ohsm_sbi = NULL;
	ohsm_debug(OHSM_LOG_RELOCATE, "Freeing the Allocation policy.\n");
	reset_sam();

	return OHSM_SUCCESS;
}

/***********************************************************
 *                                                         *
 * verify_mntpt () verify whether the mountpoint entered   *
 * is valid and whether it contain a valid ext2 partition  *
 *                                                         *
 **********************************************************/

static int
verify_mntpt(char *device, char *mountpoint)
{

	int retval = 0;
	struct nameidata nd;

	if (device[0] == '\0' || mountpoint[0] == '\0')
		return -EINVAL;

	retval = path_lookup(mountpoint, LOOKUP_OPEN, &nd);
	if (retval) {
	  ohsm_debug(OHSM_LOG_GENERIC, "Mount point lookup failed\n");
		return retval;
	}

	ohsm_debug(OHSM_LOG_GENERIC, "Verifying mount point = %s.\n", mountpoint);
	ohsm_debug(OHSM_LOG_GENERIC, "Verifying device = %s.\n", device);
	ohsm_debug(OHSM_LOG_GENERIC, "Fetched device = %s.\n",
			nd.path.mnt->mnt_devname);

	EXT2_I(nd.path.dentry->d_inode)->ohsm_home_tid = 0;
	if (!strcmp(nd.path.mnt->mnt_devname, device)) {
		ohsm_sb = nd.path.mnt->mnt_sb;
	} else {
	  ohsm_error(OHSM_LOG_GENERIC, "Invalid mountpoint %s\n", mountpoint);
		retval = -ENOENT;
		goto err_out;
	}

	BUG_ON(!ohsm_sb);

	ohsm_sbi = (struct ext2_sb_info *) (ohsm_sb->s_fs_info);
	BUG_ON(!ohsm_sbi);
	retval = OHSM_SUCCESS;
 err_out:
	path_put(&(nd.path));
	return retval;
}

/****************************************************************
 *                                                              *
 *  ohsm_realloc_mcp () actual relocation code performing       *
 *  relocation.                                                 *
 *  Relocation is done using Tricky Copy And Swap Algorithm     *
 *  inum - inode no. of the inode that is to be relocated       *
 *                                                              *
 ***************************************************************/

int
ohsm_realloc_mcp(unsigned long inum)
{

	int err = 0;
	int done;
	int blk_index = 0;
	int retval = 0;
	unsigned int swap;
	loff_t i_size;
	blkcnt_t nblocks;
	struct inode *src_ind, *dest_ind;
	struct buffer_head *src_bhptr = NULL, *dst_bhptr = NULL;
	struct buffer_head dest_bh, src_bh;
	struct nameidata nd;

	retval = path_lookup (ohsm_mountpoint, LOOKUP_OPEN, &nd);
	if (retval) {
		ohsm_debug(OHSM_LOG_RELOCATE, "Error in pathlookup.\n");
		goto out_err;
	}

	/* Get the source inode */
	src_ind = ext2_iget(ohsm_sb, inum);
	if (!src_ind)
		goto out_err;

	/* Getting the ghost inode */
	dest_ind = ext2_new_inode(nd.path.dentry->d_inode, src_ind->i_mode);
	if (!dest_ind) {
		err = OHSM_ERR_FS;
		iput(src_ind);
		goto out_err;
	}

	EXT2_I(dest_ind)->ohsm_home_tid = EXT2_I(src_ind)->ohsm_dest_tid;
	/* Locking the source inode using mutex */
	mutex_lock(&src_ind->i_mutex);

	/* Get the source inode size and number of blocks */
	i_size = i_size_read(src_ind);

	/* Calculating number of block to allocate for ghost inode */
	nblocks = (i_size >> EXT2_BLOCK_SIZE_BITS(ohsm_sb)) +
		((i_size & (ohsm_sb->s_blocksize - 1)) ? 1 : 0);

	/*  Starting Tricky Copy
	 *
	 *  Allocate a data block for ghost
	 *  Read the block into buffer head 1
	 *  Read the source block into buffer head 2
	 *  assign b_data of buffer 2 to b_data of buffer 1
	 *  Mark buffer dirty
	 *  For indirect blocks read the chain of indirect
	 *  pointers leading to data
	 *  Now ghost inode has exact data from source
	 *  Swap source i_data with ghost i_data
	 *  Finally source inode has new data blocks
	 *
	 *  so mark it dirty and delete the ghost inode.
	 */

	for (done = 0; done < nblocks; done++) {
		memset(&dest_bh, 0, sizeof(struct buffer_head));
		memset(&src_bh, 0, sizeof(struct buffer_head));

		err = ext2_get_block(src_ind, done, &src_bh, 0);
		if (err < 0) {
			err = OHSM_ERR_FS;
			ohsm_debug(OHSM_LOG_RELOCATE, "getting blocks ret_val = %d.\n", err);
			goto unlock;
		}

		if (!buffer_mapped(&src_bh))
			continue;

		dest_bh.b_state = 0;
		err = ext2_get_block(dest_ind, done, &dest_bh, 1);
		if (err < 0) {
			err = OHSM_ERR_FS;
			ohsm_debug(OHSM_LOG_RELOCATE, "getting blocks ret_val = %d.\n", err);
			goto unlock;
		}

		src_bhptr = sb_bread(ohsm_sb, src_bh.b_blocknr);
		if (!src_bhptr) {
			err = OHSM_ERR_FS;
			ohsm_debug(OHSM_LOG_RELOCATE, "Source buffer NULL.\n");
			goto unlock;
		}

		dst_bhptr = sb_bread(ohsm_sb, dest_bh.b_blocknr);
		if (!dst_bhptr) {
			err = OHSM_ERR_FS;
			ohsm_debug(OHSM_LOG_RELOCATE, "Destination buffer NULL.\n");
			goto unlock;
		}
		lock_buffer(dst_bhptr);
		memcpy(dst_bhptr->b_data, src_bhptr->b_data, src_bhptr->b_size);
		unlock_buffer(dst_bhptr);

		mark_buffer_dirty(dst_bhptr);

		brelse(src_bhptr);
		brelse(dst_bhptr);
	}

	for (blk_index = 0; blk_index < 15; blk_index++) {
		if (EXT2_I(dest_ind)->i_data[blk_index]) {
			swap = EXT2_I(src_ind)->i_data[blk_index];
			EXT2_I(src_ind)->i_data[blk_index] =
				EXT2_I(dest_ind)->i_data[blk_index];
			EXT2_I(dest_ind)->i_data[blk_index] = swap;
		}
	}

	EXT2_I(src_ind)->ohsm_home_tid = EXT2_I(src_ind)->ohsm_dest_tid;
	EXT2_I(src_ind)->ohsm_dest_tid = 0;

	dest_ind->i_blocks = src_ind->i_blocks ;
	mark_inode_dirty(src_ind);
	sync_mapping_buffers(src_ind->i_mapping);
	dest_ind->i_state &= ~I_FREEING;
	ohsm_sync_inode(src_ind);
	err = OHSM_SUCCESS;

unlock:
	iput(src_ind);
	dest_ind->i_state &= ~I_NEW;
	dest_ind->i_nlink = 0;
	iput(dest_ind);
	mutex_unlock(&src_ind->i_mutex);

out_err:
	path_put (&(nd.path));
	return err;
}


/****************************************************************
 *                                                              *
 * turn_the_tables () is used to create the sam table           *
 *                                                              *
 ***************************************************************/

void
turn_the_tables (void)
{

	int i = 0;
	unsigned int cur_tier;
	unsigned int col;
	unsigned int disks;
	unsigned long nr_groups;

	reset_sam();
	ohsm_debug(OHSM_LOG_RELOCATE, "Number of tiers = %u\n", ohsm_dev_info->nr_tiers);
	ohsm_debug(OHSM_LOG_RELOCATE, "Number of devices = %u\n", ohsm_dev_info->nr_devices);

	for (i = 0 ; i < ohsm_dev_info->dinfo_count; ++i) {
		ohsm_debug(OHSM_LOG_RELOCATE, "Number = %u\n", i);
		ohsm_debug(OHSM_LOG_RELOCATE, "Tier =  %u\n", ohsm_dev_info->device[i].tier);
		ohsm_debug(OHSM_LOG_RELOCATE, "Device = %u\n", ohsm_dev_info->device[i].dev_num);
		ohsm_debug(OHSM_LOG_RELOCATE, "Length  = %lu\n", ohsm_dev_info->device[i].len);
		ohsm_debug(OHSM_LOG_RELOCATE, "Start = %lu\n", ohsm_dev_info->device[i].begin);
	}

	for (i = 0; i < ohsm_dev_info->dinfo_count; i++) {
		cur_tier = ohsm_dev_info->device[i].tier;
		sam[cur_tier][0] = sam[cur_tier][0] + 1;
		disks = sam[cur_tier][0];
		col = disks * 2 - 1;
		sam[cur_tier][col] = ohsm_dev_info->device[i].blkgrp_start;
		sam[cur_tier][col + 1] = ohsm_dev_info->device[i].blkgrp_end;
	}

	nr_groups = ohsm_sbi->s_groups_count;
	sam[0][0] = 1;
	sam[0][1] = 0;
	sam[0][2] = nr_groups;

	for (i = 1; i <= ohsm_dev_info->dinfo_count; i++) {
		if (sam[i][0] == 0) {
			ohsm_debug(OHSM_LOG_RELOCATE, "SAM table filled.\n");
			break;
		}
		sam[i][0]++;
		disks = sam[i][0];
		col = disks * 2 - 1;

		sam[i][col] = 0;
		sam[i][col + 1] = nr_groups;
	}
	ohsm_debug(OHSM_LOG_RELOCATE, "Printing SAM after adding start and end.\n");
	debug_print_sam ();
}

/****************************************************************
 *                                                              *
 * ohsm_set_bg_dm_info is used to calculate the blockgroup      *
 * start and block group end per device                         *
 *                                                              *
 ***************************************************************/

int
ohsm_set_bg_dm_info(void)
{

	int i;
	unsigned int block_size, blk_per_grp;
	int retval = 0;
	struct nameidata nd;
	struct ext2_sb_info *sbi = NULL;
	struct super_block *sb;
	struct vfsmount *vfs = NULL;

	BUG_ON((int)(ohsm_dev_info->dinfo_count) < 0);

	retval = path_lookup (ohsm_mountpoint, LOOKUP_OPEN, &nd);
	if (retval) {
	  ohsm_error(OHSM_LOG_GENERIC, "Unable to lookup mountpoint\n");
	  return -ENOENT;
	}

	vfs = nd.path.mnt;
	sb = (struct super_block *) (vfs->mnt_sb);
	sbi = (struct ext2_sb_info *) (sb->s_fs_info);
	block_size = 1 << le32_to_cpu(sbi->s_es->s_log_block_size);
	blk_per_grp = sbi->s_blocks_per_group;

	for (i = 0; i < ohsm_dev_info->dinfo_count; i++) {
		ohsm_dev_info->device[i].blkgrp_start = (unsigned int) (ohsm_dev_info->device[i].begin/(2 * block_size * blk_per_grp));
		if (ohsm_dev_info->device[i].begin) {
			ohsm_dev_info->device[i].blkgrp_start++;
		}

		ohsm_dev_info->device[i].blkgrp_end =
			(unsigned int) ((ohsm_dev_info->device[i].begin + ohsm_dev_info->device[i].len) / (2 * block_size * blk_per_grp) - 1);

		if (ohsm_dev_info->device[i].len < (block_size * blk_per_grp)) {
			ohsm_dev_info->device[i].blkgrp_end++;
		}
	}
	path_put (&(nd.path));
	return 0;
}

/****************************************************************
 *                                                              *
 *  ohsm_get_inode_for_debug () is used for debugging           *
 *  it scans all inodes in the file system and prints           *
 *  them along with their size and tier-id                      *
 *                                                              *
 ***************************************************************/

int
ohsm_get_inodes_for_debug(void) {

	unsigned char start_ino = 0;
	int ret;
	unsigned long nr_groups;
	unsigned long i = 0;
	unsigned long nr_inodes_per_group;
	unsigned long inode_no = 0;
	struct buffer_head *bh = NULL;
	struct inode *temp_inode = NULL;
	struct ext2_inode_info *ei = NULL;
	struct dentry *dentry;

	nr_groups = ohsm_sbi->s_groups_count;
	nr_inodes_per_group = ohsm_sbi->s_inodes_per_group;

	ohsm_debug(OHSM_LOG_GENERIC, "\n\t\tFileName\t\tInode No.\tFile Size\tHome TID\n");

	for (i = 0, start_ino = ohsm_sbi->s_first_ino; i < nr_groups; i++, start_ino = 0) {
		bh = (struct buffer_head *) read_inode_bitmap(ohsm_sb, i);
		if (!bh)
		  return -EIO;

		for (inode_no = start_ino; inode_no < nr_inodes_per_group - 1; inode_no++) {
			ret = ext2_test_bit(inode_no, (unsigned long *) (bh->b_data));

			if (ret) {

				temp_inode = ext2_iget(ohsm_sb, (inode_no + 1) + i * nr_inodes_per_group);
				if (!temp_inode) {
				  ohsm_debug(OHSM_LOG_GENERIC, "ext2_iget returned NULL.\n");
					brelse(bh);
					return -EIO;
				}

				ei = EXT2_I(temp_inode);
				if (!ei) {
				  ohsm_debug(OHSM_LOG_GENERIC, "EXT2_I returned NULL.\n");
					iput(temp_inode);
					brelse(bh);
					return -EIO;
				}
				/*
				 * Extracting the file name (dentry)
				 */

				if (!list_empty(&temp_inode->i_dentry)) {
					dentry = list_entry(temp_inode->i_dentry.next, struct dentry, d_alias);
					printk("\n%25s", dentry ? (char *) dentry->d_name.name : "null");
				} else {
					printk("\nnull");
				}

				if (temp_inode->i_size < 1024) {
					printk(
					"\t\t%lu\t\t%lub\t\t%u", (unsigned long) temp_inode->i_ino,
					(unsigned long) temp_inode->i_size, (unsigned int)ei->ohsm_home_tid);
				} else {
					printk(
					"\t\t%lu\t\t%luk\t\t%u", (unsigned long) temp_inode->i_ino,
					(unsigned long) temp_inode->i_size >> 10, (unsigned int)ei->ohsm_home_tid);
				}
				iput(temp_inode);
			}
		}
		brelse(bh);
	}
	return OHSM_SUCCESS;
}

/****************************************************************
 *                                                              *
 * debug_print_sam () print the sam table conatining the        *
 * block group range per tier                                   *
 *                                                              *
 ***************************************************************/

void
debug_print_sam(void) {

	int i = 0;
	int j;

	ohsm_debug(OHSM_LOG_GENERIC, "Printing SAM Table.\n");
	for (i = 0; i < OHSM_MAX_TIER_ALLOWED; i++) {
		if (sam[i][0] == 0)
			continue;
		else
			for (j = 0; j < (OHSM_MAX_PV_PER_LVM * 2 + 1); j++) {
			  printk(" - %10lu - ", sam[i][j]);
				if (sam[i][j] == sam[0][2])
					break;
			}

		printk("\n");
	}
}

/****************************************************************
 *                                                              *
 * demo_reloc () is defined for demo. It relocates the          *
 * inode submitted by the user.                                 *
 *                                                              *
 ***************************************************************/

int
demo_reloc(struct ohsm_debug_reloc *reloc_demo) {

	int ret;
	struct inode *src_ind ;
	struct dentry *dentry;

	/* Get the source inode */
	src_ind = ext2_iget(ohsm_sb, reloc_demo->inum);

	if (unlikely(IS_ERR(src_ind))) {
	  if (PTR_ERR(src_ind) == -ESTALE) {
	    ohsm_error(OHSM_LOG_GENERIC, "deleted inode referenced: %lu\n",
		       reloc_demo->inum);
	    return -EIO;
	  } else {
	    return -ENOENT;
	  }
	}

	if (EXT2_I(src_ind)->ohsm_home_tid == reloc_demo->dest) {
			ohsm_debug(OHSM_LOG_RELOCATE, "File already on Tier: %d\n", reloc_demo->dest);
			return OHSM_SUCCESS;
	}

	if (!list_empty(&src_ind->i_dentry)) {
		dentry = list_entry(src_ind->i_dentry.next, struct dentry, d_alias);
		if (dentry)
			ohsm_debug(OHSM_LOG_RELOCATE, "Relocating %s\n", (const char *) dentry->d_name.name);
	}

	EXT2_I(src_ind)->ohsm_dest_tid = reloc_demo->dest;
	iput(src_ind);
	ret = ohsm_realloc_mcp(reloc_demo->inum);

	return ret;
}

/****************************************************************
 *                                                              *
 * device_driver related functions                              *
 *                                                              *
 ***************************************************************/

struct file_operations fops = {
	.ioctl = device_ioctl,
	.read = NULL,
	.write = NULL,
};

/****************************************************************
 *                                                              *
 * ohsm_int () disables OHSM and free all data              *
 * structures allocated                                         *
 *                                                              *
 ***************************************************************/
int
ohsm_init(void) {

	Major = register_chrdev (0, DEVICE_NAME, &fops);
	if (Major < 0) {
	  ohsm_debug(OHSM_LOG_GENERIC, "Unable to initialize driver.\n");
		return Major;
	}
	ohsm_debug(OHSM_LOG_GENERIC, "Module Initialized.\n");
	ohsm_enabled = UNSET;
	ohsm_relocating = UNSET;
	ohsm_get_tier_fptr = (void *) &ohsm_get_tier;
	return OHSM_SUCCESS;
}

/****************************************************************
 *                                                              *
 * ohsm_disable () disables OHSM and free all data              *
 * structures allocated                                         *
 *                                                              *
 ***************************************************************/
void
ohsm_cleanup(void) {

	int ret;
	ret = ohsm_disable();
	unregister_chrdev (Major, DEVICE_NAME);
	ohsm_get_tier_fptr = NULL;
	ohsm_debug(OHSM_LOG_GENERIC, "Module unloaded.\n");
}

/****************************************************************
 *                                                              *
 * ohsm_sanity_check () checks whether the ohsm is currently    *
 * enabled and the superblock of the mounted file system        *
 * exist.                                                       *
 *                                                              *
 ***************************************************************/
static inline int
ohsm_sanity_check (void)
{
  int ret = -ESTALE;
  if (ohsm_enabled && ohsm_sb)
    ret = 0;

  return ret;
}

/****************************************************************
 *                                                              *
 * ohsm_disable () disables OHSM and free all data              *
 * structures allocated                                         *
 *                                                              *
 ***************************************************************/
static int
device_ioctl (struct inode *inode, struct file *filp,
	      unsigned int cmd, unsigned long arg)
{

	int ret = 0;
	struct ohsm_debug_reloc *reloc_demo;
	struct ohsm_inode_info *ohsm_inode;

	switch (cmd) {

	case IOC_OHSM_ENABLE:

		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;

		if (ohsm_enabled) {
		  ohsm_debug(OHSM_LOG_GENERIC, "OHSM already enabled.\n");
			return -EEXIST;
		}

		if (!ohsm_admin_info)
			ohsm_admin_info = (struct ohsm_struct_for_ioctl *)kmalloc(sizeof(struct ohsm_struct_for_ioctl), GFP_KERNEL);

		if (copy_from_user(ohsm_admin_info, (struct ohsm_struct_for_ioctl __user *)arg,
					sizeof(*ohsm_admin_info)))
			return -EFAULT;

		ohsm_debug(OHSM_LOG_GENERIC, "Request to enable OHSM.\n");
		ohsm_debug(OHSM_LOG_GENERIC, "The Mount Point = %s.\n", ohsm_admin_info->mountpoint);

	  ret = verify_mntpt(ohsm_admin_info->device, ohsm_admin_info->mountpoint);
	  if (ret)
	    return ret;

	  if (!ohsm_dev_info)
	    ohsm_dev_info = (struct ohsm_tier_dev_info *)kmalloc(sizeof(struct ohsm_tier_dev_info), GFP_KERNEL);

	  if (!ohsm_dev_info) {
	    ret = -ENOMEM;
	    goto free_all;
	  }

	  if (!ohsm_allocpol)
	    ohsm_allocpol = (struct ohsm_allocation_policy *)kmalloc(sizeof(struct ohsm_allocation_policy), GFP_KERNEL);

	  if (!ohsm_allocpol) {
	    ret = -ENOMEM;
	    goto free_all;
	  }

	  if (!ohsm_relocpol)
	    ohsm_relocpol = (struct ohsm_reloc_policy *)kmalloc(sizeof(struct ohsm_reloc_policy), GFP_KERNEL);

	  if (!ohsm_relocpol) {
	    ret = -ENOMEM;
	    goto free_all;
	  }


	  strncpy(ohsm_mountpoint, ohsm_admin_info->mountpoint, OHSM_MAX_MNTPT_LEN);

	  /* Copy other data from userspace */
	  if (copy_from_user(ohsm_dev_info,
			     (__force struct ohsm_tier_dev_info __user *)(ohsm_admin_info->ptr_dev_file),
			     sizeof(struct ohsm_tier_dev_info))) {
	    ohsm_error(OHSM_LOG_GENERIC, "copying dev_info\n");
	    ret = -EFAULT;
	    goto free_all;
	  }

	  if (copy_from_user(ohsm_allocpol,
			     (__force struct ohsm_allocation_policy __user *)(ohsm_admin_info->ptr_alloc_policy),
			     sizeof(struct ohsm_allocation_policy))) {
	    ohsm_error(OHSM_LOG_GENERIC, "copying allocation policy\n");
	    ret = -EFAULT;
	    goto free_all;
	  }

	  if (copy_from_user(ohsm_relocpol,
			     (__force struct ohsm_reloc_policy __user *)(ohsm_admin_info->ptr_reloc_policy),
			     sizeof(struct ohsm_reloc_policy))) {
	    ohsm_error(OHSM_LOG_GENERIC, "copying relocation policy\n");
	    ret = -EFAULT;
	    goto free_all;
	  }

	  BUG_ON(!ohsm_admin_info || !ohsm_allocpol || !ohsm_relocpol || !ohsm_dev_info);

	  ret = ohsm_set_bg_dm_info ();
	  if (ret)
	    goto free_all;

	  turn_the_tables();
	  ohsm_enabled = 1;

	  return OHSM_SUCCESS;
	free_all:
	  if (ohsm_dev_info)
	    kfree(ohsm_dev_info);
	  if (ohsm_allocpol)
	    kfree(ohsm_allocpol);
	  if (ohsm_relocpol)
	    kfree(ohsm_relocpol);
	  return ret;

	case IOC_OHSM_DISABLE:

		if (!capable (CAP_SYS_ADMIN))
			return -EPERM;

		ret = ohsm_disable();
		if (ret)
			ohsm_debug(OHSM_LOG_GENERIC, "OHSM Relocating or already disabled.\n");
		return ret;

	case IOC_OHSM_DISPLAY_SAM:

		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		debug_print_sam();
		return OHSM_SUCCESS;

	case IOC_OHSM_DISPLAY_INODES:

		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		ret = ohsm_get_inodes_for_debug();
		return ret;

	case IOC_OHSM_DEMO_RELOC:

		if (!capable (CAP_SYS_ADMIN))
			return -EPERM;
		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		reloc_demo = (struct ohsm_debug_reloc *) arg;
		ret = demo_reloc(reloc_demo);
		return ret;

	case IOC_OHSM_STATUS:

		return !ohsm_enabled;

	case IOC_OHSM_GET_TIER_INFO:

		ret = ohsm_sanity_check();
		if (ret)
			return ret;

		BUG_ON(ohsm_dev_info == NULL);
		if (copy_to_user((void __user *)arg, (void *)ohsm_dev_info, sizeof(struct ohsm_tier_dev_info)))
			return -EFAULT;

		return OHSM_SUCCESS;

	case IOC_OHSM_GET_ALLOC_POL:

		ret = ohsm_sanity_check();
		if (ret)
			return ret;

		BUG_ON(ohsm_allocpol == NULL);
		if (copy_to_user((void __user *)arg, (void *)ohsm_allocpol, sizeof(struct ohsm_allocation_policy)))
			return -EFAULT;
		return OHSM_SUCCESS;

	case IOC_OHSM_GET_RELOC_POL:

		ret = ohsm_sanity_check();
		if (ret)
			return ret;

		BUG_ON(ohsm_relocpol == NULL);
		if (copy_to_user((void __user *)arg, (void *)ohsm_relocpol, sizeof(struct ohsm_reloc_policy)))
			return -EFAULT;

		return OHSM_SUCCESS;

	case IOC_OHSM_SET_ALLOC_POL:

		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		memset(ohsm_allocpol, 0, sizeof (struct ohsm_allocation_policy));
		if (copy_from_user((void *)ohsm_allocpol, (void __user *)arg, sizeof(struct ohsm_allocation_policy))) {
		  ohsm_error(OHSM_LOG_GENERIC, "copying allocation policy\n");
			return -EFAULT;
		}
		return OHSM_SUCCESS;

	case IOC_OHSM_SET_RELOC_POL:
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		memset(ohsm_relocpol, 0, sizeof (struct ohsm_reloc_policy));
		if (copy_from_user((void *)ohsm_relocpol, (void __user *)arg,
					sizeof(struct ohsm_reloc_policy))) {
		  ohsm_error(OHSM_LOG_RELOCATE, "copying relocation policy\n");
			return -EFAULT;
		}
		return OHSM_SUCCESS;

	case IOC_OHSM_SET_TIER_INFO:
		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		ret = ohsm_sanity_check();
		if (ret)
			return ret;
		memset(ohsm_dev_info, 0, sizeof(struct ohsm_tier_dev_info));
		if (copy_from_user((void *)ohsm_dev_info, (void __user *)arg, sizeof(struct ohsm_tier_dev_info))) {
		  ohsm_error(OHSM_LOG_GENERIC, "copying tier device information\n");
			return -EFAULT;
		}
		ohsm_set_bg_dm_info();
		turn_the_tables();
		return OHSM_SUCCESS;

	case IOC_OHSM_GET_INO_INFO:

		if (!capable(CAP_SYS_ADMIN))
			return -EPERM;
		ohsm_inode = (struct ohsm_inode_info *)kmalloc(sizeof(struct ohsm_inode_info), GFP_KERNEL);
		
		if (!ohsm_inode)
			return -ENOMEM;

		if (copy_from_user((void *)ohsm_inode, (void __user *)arg, sizeof(struct ohsm_inode_info))) {
		  ohsm_error(OHSM_LOG_GENERIC, "copying inode information\n");
			kfree(ohsm_inode);
			return -EFAULT;
		}

		ret = ohsm_get_inode_info(ohsm_inode);
		if (ret)
			return ret;
		if (copy_to_user((void __user *)arg, (void  *)ohsm_inode, sizeof(struct ohsm_inode_info))) {
		  ohsm_error(OHSM_LOG_GENERIC, "copying inode information to user\n");
			kfree(ohsm_inode);
		  	return -EFAULT;
		}
		return OHSM_SUCCESS;

	default:
	  ohsm_error(OHSM_LOG_GENERIC, "Unknown ioctl\n");
	  return -EINVAL;
	}
	return OHSM_SUCCESS;
}
module_init(ohsm_init);
module_exit(ohsm_cleanup);
MODULE_LICENSE ("GPL v2");
MODULE_AUTHOR ("Rishi Bhushan Agrawal, Vineet Agarwal, Rohit Sharma");
